package net.aiion.weave.impl.cards.xml;

import java.util.Stack;

import net.aiion.weave.asm.Label;
import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.CardXmlNames;
import net.aiion.weave.spi.cards.events.CardEvent;
import net.aiion.weave.spi.cards.expressions.CardExpressionResolverService;
import net.aiion.weave.spi.injection.annotations.Inject;
import net.aiion.weave.spi.naming.annotations.Named;
import net.aiion.weave.spi.naming.annotations.UserScoped;
import net.aiion.weave.spi.xml.XmlContext;
import net.aiion.weave.spi.xml.XmlSink;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

@UserScoped
@Named
public class IfTransformation implements XmlSink
{
    private static final String PROP_INTERNAL_NAME = "internalName";

    private static final int INDEX_CARD_CONTEXT = 1;
    private static final int INDEX_TEST_VALUE = 2;

    private static final String CARD_EXPRESSION_RESOLVER_SERVICE_FIELD_NAME = "expressionResolver$IfXmlSink";

    private static final String IF_ATTRIBUTE_TEST_NAME = "test";

    @Inject
    private BytecodeGenerator bytecodeGenerator;

    private Stack<Label> labelStack;

    public IfTransformation() {
        labelStack = new Stack<>();
    }

    @Override
    public void handleXmlEvent(XmlContext aXmlContext) {
        final XmlEvent xmlEvent = aXmlContext.getXmlEvent();
        switch (xmlEvent.getType()) {
        case CardEvent.CARD_CLASS_START:
            bytecodeGenerator.startField(Opcodes.ACC_PRIVATE, CARD_EXPRESSION_RESOLVER_SERVICE_FIELD_NAME,
                CardExpressionResolverService.class);
            bytecodeGenerator.endField();
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case CardEvent.CARD_PROCESS_CARD_METHOD_START:
            bytecodeGenerator.loadReference(0);
            bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
            bytecodeGenerator.invokeInterface(CardContext.class, "getExpressionResolverService",
                CardExpressionResolverService.class);
            bytecodeGenerator.storeField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                CARD_EXPRESSION_RESOLVER_SERVICE_FIELD_NAME, CardExpressionResolverService.class);
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case XmlEvent.START_ELEMENT:
            handleStartElementXmlEvent(aXmlContext, xmlEvent.asElementStart());
            break;
        case XmlEvent.END_ELEMENT:
            handleEndElementXmlEvent(aXmlContext, xmlEvent.asElementEnd());
            break;
        default:
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        }
    }

    private void handleStartElementXmlEvent(final XmlContext aXmlContext, final ElementStartXmlEvent aXmlStartElement) {
        if (!CardXmlNames.IF.equals(aXmlStartElement.getName())) {
            aXmlContext.forwardXmlEvent(aXmlStartElement);
            return;
        }

        final Label nextCaseLabel = new Label();
        labelStack.push(nextCaseLabel);

        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            CARD_EXPRESSION_RESOLVER_SERVICE_FIELD_NAME, CardExpressionResolverService.class);
        bytecodeGenerator.pushConstant(aXmlStartElement.getAttribute(IF_ATTRIBUTE_TEST_NAME).getValue());
        bytecodeGenerator.invokeInterface(CardExpressionResolverService.class, "readExpression", Object.class,
            String.class);
        bytecodeGenerator.storeReference(INDEX_TEST_VALUE);

        bytecodeGenerator.loadReference(INDEX_TEST_VALUE);
        bytecodeGenerator.jumpNull(nextCaseLabel);
        bytecodeGenerator.loadStatic(Boolean.class, "FALSE", Boolean.class);
        bytecodeGenerator.loadReference(INDEX_TEST_VALUE);
        bytecodeGenerator.invokeVirtual(Boolean.class, "equals", boolean.class, Object.class);
        bytecodeGenerator.jumpNotEquals(nextCaseLabel);
    }

    private void handleEndElementXmlEvent(final XmlContext aXmlContext, final ElementEndXmlEvent aXmlEndElement) {
        if (!CardXmlNames.IF.equals(aXmlEndElement.getName())) {
            aXmlContext.forwardXmlEvent(aXmlEndElement);
        }

        bytecodeGenerator.markLabel(labelStack.pop());
        bytecodeGenerator.markFrameSame();
    }
}
